﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CLFramework.Infrastructure;

namespace CLFramework.Caching
{

    /// <summary>
    /// 从数据库获取实体对象的方法代理
    /// </summary>
    /// <typeparam name="TKey">实体主键的类型</typeparam>
    /// <typeparam name="TValue">实体的类型</typeparam>
    /// <param name="keys">键集合</param>
    /// <param name="vals">实体集合</param>
    public delegate void GetEntitiesFromDatabaseDelegate<TKey, TValue>(TKey[] keys, ref Dictionary<TKey, TValue> entities);

    public delegate TValue GetEntityFromDatabaseDelegate<TKey, TValue>(TKey key);

    /// <summary>
    /// 根据Id集合从CacheEntity中获取数据——首先从缓存获取缓存了的集合，然后从数据库获取未缓存的集合并缓存
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="query"></param>
    public delegate List<TValue> GetEntitiesFromCacheEntityDelegate<TKey, TValue>(TKey[] ids, ref Dictionary<TKey, TValue> entities, bool entitiesCacheable);

    /// <summary>
    /// 缓存实体管理类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CacheEntityTable<TKey, TValue> : ICachedTable
    {
        #region Members

        int entityCachedSeconds = 180;
        int maxEntityCount = 1000;
        string cacheKey;
        Dictionary<string, CachedEntity<TValue>> cachedEntities = new Dictionary<string, CachedEntity<TValue>>();
        object syncObj = new object();
        Dictionary<string, CacheTimestamp> timestamps = new Dictionary<string, CacheTimestamp>();
        object timestampsLockObj = new object();

        #endregion

        #region Ctor.

        public CacheEntityTable(string cacheKey)
        {
            this.cacheKey = cacheKey;
        }

        #endregion

        /// <summary>
        /// Add an entity to cache table
        /// </summary>
        /// <param name="key"></param>
        /// <param name="entity"></param>
        public void Cache(TKey key, TValue entity)
        {
            if (entity == null)
                return;

            string entityKey = key.ToString();

            CachedEntity<TValue> cachedEntity;
            if (!cachedEntities.TryGetValue(entityKey, out cachedEntity))
            {
                cachedEntity = new CachedEntity<TValue>(entity);
                cachedEntity.Key = key;
            }
            cachedEntity.Object = entity;
            lock (syncObj)
            {
                cachedEntities[entityKey] = cachedEntity;
            }
        }

        /// <summary>
        /// Get an entity by key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public TValue Get(TKey key)
        {
            TValue value;
            TryGetValue(key, out value);
            return value;
        }

        /// <summary>
        /// Try to get an entiry by key 
        /// </summary>
        /// <param name="key">cache key</param>
        /// <param name="value">Out the entry</param>
        /// <returns>Whether the entiry exists</returns>
        public bool TryGetValue(TKey key, out TValue value)
        {
            string entityKey = key.ToString();
            CachedEntity<TValue> cachedEntity;
            value = default(TValue);
            bool exists = cachedEntities.TryGetValue(entityKey, out cachedEntity);
            if (exists)
            {
                if (CacheEntityDuration == 0 // 为0不判断超时
                    || cachedEntity.LastUpdate.AddSeconds(this.CacheEntityDuration) >= DateTime.Now) // 判断超时
                {
                    value = cachedEntity.Object;
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool ContainsKey(TKey key)
        {
            return cachedEntities.ContainsKey(key.ToString());
        }

        public List<string> Keys
        {
            get
            {
                return cachedEntities.Keys.ToList();
            }
        }

        //add by: leoliao 
        //on: 2012-05-15
        public List<TValue> Values
        {
            get
            {
                return cachedEntities.Values.Select(c=>c.Object).ToList();
            }
        }

        /// <summary>
        /// Remove the entry by key
        /// </summary>
        /// <param name="key"></param>
        /// <param name="masAsExpiration">If mark a cache entity as expiration, cachemanager will sync the timestamp to other service</param>
        public void Remove(TKey key, bool masAsExpiration = false)
        {
            string entityKey = key.ToString();
            if (masAsExpiration)
            {
                TValue entity = Get(key);
                if (entity != null)
                {
                    lock (timestampsLockObj)
                    {
                        // Add timestamp to a temporary dictionary, we will sync them to other services later.
                        timestamps[entityKey.ToString()] = new CacheTimestamp() { TableName = CacheKey, RowKey = entityKey.ToString(), TimeStamp = DateTime.Now };
                    }
                }
            }

            lock (syncObj)
            {
                cachedEntities.Remove(entityKey);
            }
        }

        public void RemoveByValue(TValue value)
        {
            var key = cachedEntities.Where(kv => kv.Value.Object.Equals(value)).FirstOrDefault();
            if (!key.Key.Equals(default(TKey)))
                lock (syncObj)
                    cachedEntities.Remove(key.Key);
        }

        public void Remove(TKey[] keys)
        {
            foreach (var key in keys)
            {
                Remove(key);
            }
        }

        public void RemoveExpiredEntity(TKey key, DateTime timeStamp)
        {
            CachedEntity<TValue> cachedEntity;
            if (cachedEntities.TryGetValue(key.ToString(), out cachedEntity))
            {
                if (cachedEntity.Created < timeStamp)
                {
                    lock (syncObj)
                    {
                        cachedEntities.Remove(key.ToString());
                    }
                }
            }
        }

        public void RemoveExpiredEntities(List<CacheTimestamp> timestamps)
        {
            CachedEntity<TValue> tv;
            List<TKey> ks;
            List<string> tobeRemoveKeys = new List<string>();

            if (timestamps != null)
            {
                foreach (CacheTimestamp stamp in timestamps)
                {
                    if (stamp.TableName != CacheKey)
                        continue;
                    string key = stamp.RowKey;
                    if (cachedEntities.TryGetValue(key, out tv))
                    {
                        if (tv.Created < stamp.TimeStamp)
                        {
                            tobeRemoveKeys.Add(key);
                        }
                    }
                }
            }

            if (tobeRemoveKeys.Count > 0)
            {
                RemoveEntities(tobeRemoveKeys);
            }
        }

        public void RemoveEntities(List<string> tobeRemoveKeys)
        {
            lock (syncObj)
            {
                foreach (string key in tobeRemoveKeys)
                {
                    cachedEntities.Remove(key);
                }
            }
        }

        public void RemoveEntities(List<Guid> tobeRemoveKeys)
        {
            lock (syncObj)
            {
                foreach (Guid key in tobeRemoveKeys)
                {
                    cachedEntities.Remove(key.ToString());
                }
            }
        }

        public void RemoveEntities(List<int> tobeRemoveKeys)
        {
            lock (syncObj)
            {
                foreach (int key in tobeRemoveKeys)
                {
                    cachedEntities.Remove(key.ToString());
                }
            }
        }

        public TValue GetEntity(TKey key, GetEntityFromDatabaseDelegate<TKey, TValue> getEntityFromDatabase, bool cacheable = true)
        {
            TValue entity = default(TValue);
            if (cacheable)
            {
                // if we found a cache item
                if (TryGetValue(key, out entity))
                {
                    return entity;
                }
            }
            entity = getEntityFromDatabase(key);
            Cache(key, entity);
            return entity;
        }

        public Dictionary<TKey, TValue> GetEntityDictionary(TKey[] keys, GetEntitiesFromDatabaseDelegate<TKey, TValue> getEntitiesFromDatabase, bool cacheable = true)
        {
            List<TKey> uncachedKeys = new List<TKey>(); // the keys of the uncached entities
            Dictionary<TKey, TValue> entitiesTable = new Dictionary<TKey, TValue>(); // A dictionary is used to storage all the entities
            // First, check the cache table, get the cached entities from cache table
            TValue entity;
            foreach (TKey key in keys)
            {
                entity = default(TValue);
                CachedEntity<TValue> tv;

                if (cacheable) // if we don't read from cache, skip it
                {
                    if (cachedEntities.TryGetValue(key.ToString(), out tv))
                        entity = tv.Object;
                }

                if (entity != null)
                    entitiesTable[key] = entity; // Storage the cached entity to entitiesTable
                else
                    uncachedKeys.Add(key);
            }

            // Then get the uncached entities from DB
            if (uncachedKeys.Count > 0)
            {
                Dictionary<TKey, TValue> entitiesTableFromDb = null;
                getEntitiesFromDatabase(uncachedKeys.ToArray(), ref entitiesTableFromDb);
                foreach (TKey key in entitiesTableFromDb.Keys)
                {
                    entitiesTable[key] = entitiesTableFromDb[key]; // Storage the entity to entitiesTable
                }
            }
            return entitiesTable;
        }

        public List<TValue> GetEntities(TKey[] keys, ref Dictionary<TKey, TValue> entitiesTable, bool cacheable,GetEntitiesFromDatabaseDelegate<TKey, TValue> getEntitiesFromDatabase)
        {
            return GetEntities(keys, ref entitiesTable, cacheable, getEntitiesFromDatabase, true);
        }

        /// <summary>
        /// 根据Key集合获取实体集合
        /// </summary>
        /// <param name="postIds"></param>
        /// <returns></returns>
        public List<TValue> GetEntities(TKey[] keys, ref Dictionary<TKey, TValue> entitiesTable, bool cacheable,GetEntitiesFromDatabaseDelegate<TKey, TValue> getEntitiesFromDatabase, bool refEntitiesTable)
        {
            List<TValue> entities = new List<TValue>();
            if (entitiesTable == null)
                entitiesTable = new Dictionary<TKey, TValue>();

            List<TKey> uncachedKeys = new List<TKey>(); // 缓存中没有的key列表

            TValue entity;

            // 先检索缓存
            //
            foreach (TKey key in keys)
            {
                entity = default(TValue);
                CachedEntity<TValue> tv;

                if (cacheable)
                {
                    if (cachedEntities.TryGetValue(key.ToString(), out tv))
                        entity = tv.Object;
                }

                if (entity != null)
                {
                    entitiesTable[key] = entity;
                }
                else
                    uncachedKeys.Add(key);
            }

            // 如果有没有被Cache的项，从数据库中读取
            //
            if (uncachedKeys.Count > 0)
            {
                if (refEntitiesTable)
                {
                    getEntitiesFromDatabase(uncachedKeys.ToArray(), ref entitiesTable); // 填充数据
                }
                else
                {
                    Dictionary<TKey, TValue> entitiesTableFromDb = new Dictionary<TKey, TValue>();
                    getEntitiesFromDatabase(uncachedKeys.ToArray(), ref entitiesTableFromDb); // 填充数据
                    foreach (TKey key in entitiesTableFromDb.Keys)
                    {
                        entitiesTable[key] = entitiesTableFromDb[key];
                    }
                }
            }

            foreach (TKey key in keys)
            {
                // 如果key对应的实体不存在，跳过
                //
                if (entitiesTable.TryGetValue(key, out entity))
                {
                    entities.Add(entity);
                }
                else
                    entity = default(TValue); // 如果DB中不存在，依然记录到缓存表中，避免不存在的ID会绕过缓存重复读取数据库
                //缓存
                if (cacheable)
                    Cache(key, entity);
            }
            return entities;
        }
        
        /// <summary>
        /// Get paged entities
        /// </summary>
        /// <param name="keys"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="entitiesCacheable"></param>
        /// <param name="getEntitiesFromDatabase"></param>
        /// <returns></returns>
        public PagedResult<TValue> GetEntitySet(List<TKey> keys, int pageIndex, int pageSize, bool entitiesCacheable, GetEntitiesFromDatabaseDelegate<TKey, TValue> getEntitiesFromDatabase)
        {
            TKey[] pagedKeys = GetPagedKeys<TKey>(keys, pageIndex, pageSize); // get the paged keys
            
            Dictionary<TKey, TValue> placesTable = new Dictionary<TKey, TValue>();
            List<TValue> list = GetEntities(pagedKeys, ref placesTable, entitiesCacheable,
                getEntitiesFromDatabase);

            PagedResult<TValue> set = new PagedResult<TValue>(list, pageIndex, pageSize, keys.Count);

            return set;
        }

        /// <summary>
        /// Get a page of keys from a full keys array by pageIndex and pageSize
        /// </summary>
        /// <param name="allKeys">all the keys</param>
        /// <param name="pageIndex">page index, from 0</param>
        /// <param name="pageSize">page size</param>
        /// <returns>a page of keys</returns>
        public static int[] GetPagedKeys(List<int> allKeys, int pageIndex, int pageSize)
        {
            return GetPagedKeys<int>(allKeys, pageIndex, pageSize);
        }

        /// <summary>
        /// Get a page of keys from a full keys array by pageIndex and pageSize
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="allKeys"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static T[] GetPagedKeys<T>(List<T> allKeys, int pageIndex, int pageSize)
        {
            T[] keys;
            int start, end, total, pageCount;
            start = pageIndex * pageSize;
            total = allKeys.Count;
            pageCount = total / pageSize;
            if ((total % pageSize) > 0)
                pageCount++;

            if (start > total - 1)
            {
                start = (pageCount - 1) * pageSize;
            }
            if (start < 0)
                start = 0;
            end = start + pageSize - 1;
            if (end > allKeys.Count - 1)
                end = allKeys.Count - 1;
            keys = new T[end - start + 1];
            allKeys.CopyTo(start, keys, 0, keys.Length);

            return keys;
        }

        #region ICachedTable Members

        public void RemoveExpiredEntities()
        {
            CachedEntity<TValue> tv;
            List<string> ks;
            List<string> tobeRemoveKeys = new List<string>();

            // EntityCachedSeconds为0表示长效缓存
            //
            if (entityCachedSeconds > 0)
            {
                ks = new List<string>(cachedEntities.Keys);
                foreach (string key in ks)
                {
                    if (cachedEntities.TryGetValue(key, out tv))
                    {
                        if (tv.LastActivity < DateTime.UtcNow.AddSeconds(-entityCachedSeconds))
                        {
                            tobeRemoveKeys.Add(key);
                        }
                    }
                }
            }

            if (tobeRemoveKeys.Count > 0)
            {
                RemoveEntities(tobeRemoveKeys);
            }

            if (cachedEntities.Count > MaxEntityCount)
            {
                List<CachedEntity<TValue>> objs = new List<CachedEntity<TValue>>(cachedEntities.Values);
                objs.Sort();
                int len = cachedEntities.Count - MaxEntityCount;
                CachedEntity<TValue>[] arr = new CachedEntity<TValue>[len];
                objs.CopyTo(0, arr, 0, len);

                lock (syncObj)
                {
                    foreach (CachedEntity<TValue> obj in arr)
                    {
                        TKey key = (TKey)obj.Key;
                        cachedEntities.Remove(key.ToString());
                    }
                }
            }
        }

        public void RemoveEntitiesByTimeStamp(List<CacheTimestamp> stamps)
        {

        }

        public void RemoveAll()
        {
            lock (syncObj)
            {
                cachedEntities = new Dictionary<string, CachedEntity<TValue>>();
            }
        }

        public string CacheKey
        {
            get
            {
                return cacheKey;
            }
        }

        public int CacheEntityDuration
        {
            get { return entityCachedSeconds; }
            set { entityCachedSeconds = value; }
        }

        public int MaxEntityCount
        {
            get { return maxEntityCount; }
            set { maxEntityCount = value; }
        }

        public string CacheType
        {
            get
            {
                return string.Format("{0}-->{1}", typeof(TKey), typeof(TValue));
            }
        }

        public int KeysCount
        {
            get
            {
                return cachedEntities.Keys.Count;
            }
        }

        public int ValuesCount
        {
            get
            {
                return cachedEntities.Values.Count;
            }
        }

        #endregion
    }

}
